﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Threading;

// TODO: ljm >>> refactor
public sealed class ArrayPool<T>
{
    private sealed class Bucket
    {
        private readonly int _id;
        private readonly int _bufferLength;
        private readonly T[][] _buffers;

        public int bufferLength { get { return _bufferLength; } }
        private int _index;

        internal Bucket(int bufferLength, int bufferNums, int id)
        {
            _id = id;
            _bufferLength = bufferLength;
            _buffers = new T[bufferNums][];
        }

        internal T[] Rent()
        {
            T[][] buffers = _buffers;
            T[] buffer = null;

            bool allocateBuffer = false;
            if (_index < buffers.Length)
            {
                buffer = buffers[_index];
                buffers[_index++] = null;
                allocateBuffer = buffer == null;
            }

            if (allocateBuffer)
            {
                buffer = new T[_bufferLength];
            }

            return buffer;
        }

        internal void Return(T[] array)
        {
            if (array.Length != _bufferLength)
            {
                throw new ArgumentException("Buffer Not From Pool");
            }
            bool returned;

            returned = _index != 0;
            if (returned)
            {
                _buffers[--_index] = array;
            }
        }
    
    }

    private const int DefaultMaxArrayLength = 1024 * 1024;
    private const int DefaultMaxNumberOfArraysPerBucket = 50;

    private readonly Bucket[] _buckets;

    public ArrayPool() : this(DefaultMaxArrayLength, DefaultMaxNumberOfArraysPerBucket) { }

    public ArrayPool(int maxArrayLength, int maxArraysPerBucket)
    {
        _buckets = new Bucket[GetBucketSize(maxArrayLength) + 1];
        //Debug.Log("Init Bucket Size = " + (_buckets.Length - 1));
        int poolId = Id;
        for (int i = 0; i < _buckets.Length; ++i)
        {
            _buckets[i] = new Bucket(GetMaxSizeForBucket(i), maxArraysPerBucket, poolId);
        }
    }

    private int Id => GetHashCode();

    public T[] Rent(int size)
    {
        if (size < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(size));
        }
        if (size == 0)
        {
            return Array.Empty<T>();
        }

        T[] buffer;
        int index = GetBucketSize(size);
        if (index < _buckets.Length)
        {
            int i = index;
            do
            {
                buffer = _buckets[i].Rent();
                if (buffer != null)
                {
                    //Debug.Log("Rent Size = " + size + " From Bucket, BucketIndex=" + index + " BucketSize=" + buffer.Length);
                    return buffer;
                }
            }
            while (++i < _buckets.Length && i != index + 2);        // 最多试2次

            // bucket里已经没有可用的了，直接New吧
            buffer = new T[_buckets[index].bufferLength];
            //Debug.Log("Rent Size = " + size + " From Bucket, But Bucket Is Full");
        }
        else
        {
            // 没有对应的Bucket，直接New吧
            buffer = new T[size];
            //Debug.Log("Rent Size = " + size + " From Bucket, But No Bucket Exist!!");
        }
        return buffer;
    }

    public void Return(T[] array, bool isClean = false)
    {
        if (array == null)
        {
            throw new ArgumentNullException(nameof(array));
        }
        if (array.Length == 0)
        {
            return;
        }

        int bucketIndex = GetBucketSize(array.Length);
        bool haveBucket = bucketIndex < _buckets.Length;
        if (haveBucket)
        {
            if (isClean)
            {
                Array.Clear(array, 0, array.Length);
            }

            _buckets[bucketIndex].Return(array);
        }
    }

    public static int GetMaxSizeForBucket(int binIndex)
    {
        return 16 << binIndex;
    }

    public static int GetBucketSize(int maxArrayLength)
    {
        if (maxArrayLength <= 0)
        {
            return 0;
        }
        maxArrayLength--;
        maxArrayLength |= maxArrayLength >> 1;
        maxArrayLength |= maxArrayLength >> 2;
        maxArrayLength |= maxArrayLength >> 4;
        maxArrayLength |= maxArrayLength >> 8;
        maxArrayLength |= maxArrayLength >> 16;
        ++maxArrayLength;

        int n = 0;
        while ((maxArrayLength = (maxArrayLength >> 1)) != 0)
        {
            n++;
        }
        if (n < 4)
        {
            return 0;
        }
        else
        {
            return n - 4;
        }
    }
}

public static class ListPool<T>
{
    private static readonly ObjectPool<List<T>> _listPool = new ObjectPool<List<T>>(null, delegate (List<T> l)
    {
        l.Clear();
    });

    public static List<T> Get()
    {
        return _listPool.Get();
    }

    public static void Release(List<T> toRelease)
    {
        _listPool.Release(toRelease);
    }
    
    /// <summary>
    /// 回收list，并且置空引用
    /// </summary>
    /// <param name="toRelease"></param>
    public static void Release(ref List<T> toRelease)
    {
        _listPool.Release(toRelease);
    }
}

public static class DictPool<T, TV>
{
    private static readonly ObjectPool<Dictionary<T, TV>> _dictPool = new ObjectPool<Dictionary<T, TV>>(null, delegate (Dictionary<T, TV> dict)
    {
        dict.Clear();
    });

    public static Dictionary<T, TV> Get()
    {
        return _dictPool.Get();
    }

    public static void Release(Dictionary<T, TV> toRelease)
    {
        _dictPool.Release(toRelease);
    }
}
